using System;
using System.Collections.Generic;
using System.Net;
using System.Security;
using System.Web;
using System.Windows;
using System.Xml;
using TweetSharp.Model;
using TweetSharp.Twitter.Extensions;
using TweetSharp.Twitter.Fluent;
using TweetSharp.Twitter.Model;

namespace TwitterLib
{
    /// <summary>
    /// .NET wrapper for interacting with the Twitter API
    /// </summary>
    public class TwitterNet : IServiceApi
    {
        #region Private Fields
        private string username;
        private string publicTimelineUrl;
        private string friendsTimelineUrl;
        private string userTimelineUrl;
        private string repliesTimelineUrl;
        private string directMessagesUrl;
        private string updateUrl;
        private string friendsUrl;
        private string followersUrl;
        private string userShowUrl;
        private string sendMessageUrl;
        private string destroyUrl;
        private string destroyDirectMessageUrl;
        private string createFriendshipUrl;
        private string rateLimitStatusUrl;
        private string verifyCredentialsUrl;
        private string tweetUrl;
        private string format;
        private IWebProxy webProxy = HttpWebRequest.DefaultWebProxy;  // Jason Follas: Added initialization
        private string twitterServerUrl;                              // Jason Follas

        private OAuthToken _requestToken;
        private string _pin = "";

        private User currentLoggedInUser;

        // REMARK: might need to fix this for globalization
        private readonly string twitterCreatedAtDateFormat = "ddd MMM dd HH:mm:ss zzzz yyyy"; // Thu Apr 26 01:36:08 +0000 2007
        private readonly string twitterSinceDateFormat = "ddd MMM dd yyyy HH:mm:ss zzzz";

        private static int characterLimit;
        private string clientName;

        #endregion

        #region Public Properties

        public string AccessToken { get; set; }
        public string AccessTokenSecret { get; set; }

        public User CurrentlyLoggedInUser
        {
            get
            {
                if (null == currentLoggedInUser)
                {
                    var token = new OAuthToken { Token = AccessToken, TokenSecret = AccessTokenSecret };
                    currentLoggedInUser = Login(token);
                }
                return currentLoggedInUser;
            }
            set
            {
                currentLoggedInUser = value;
            }
        }

        /// <summary>
        /// Twitter username
        /// </summary>
        public string UserName
        {
            get { return (null == currentLoggedInUser ? String.Empty : currentLoggedInUser.Name); }
        }

        /// <summary>
        /// Web proxy to use when communicating with the Twitter service
        /// </summary>
        public IWebProxy WebProxy
        {
            get { return webProxy; }
            set { webProxy = value; }
        }

        /// <summary>
        /// URL of the Twitter host.  Defaults to http://twitter.com/.  Why would
        /// you need to override this?  Think: Alternate endpoints, like other
        /// services with identical API's, or maybe a Twitter Proxy.
        /// </summary>
        public string TwitterServerUrl
        {
            get
            {
                if (String.IsNullOrEmpty(twitterServerUrl))
                    return "http://twitter.com/";
                else
                    return twitterServerUrl;
            }
            set
            {
                twitterServerUrl = value;

                if (!twitterServerUrl.EndsWith("/"))
                    twitterServerUrl += "/";
            }
        }

        /// <summary>
        ///  Url to the Twitter Public Timeline. Defaults to http://twitter.com/statuses/public_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string PublicTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(publicTimelineUrl))
                    return TwitterServerUrl + "statuses/public_timeline";
                else
                    return publicTimelineUrl;
            }
            set { publicTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the Twitter Friends Timeline. Defaults to http://twitter.com/statuses/friends_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string FriendsTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(friendsTimelineUrl))
                    return TwitterServerUrl + "statuses/friends_timeline";
                else
                    return friendsTimelineUrl;
            }
            set { friendsTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the user's timeline. Defaults to http://twitter.com/statuses/user_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string UserTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(userTimelineUrl))
                    return TwitterServerUrl + "statuses/user_timeline";
                else
                    return userTimelineUrl;
            }
            set { userTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the 20 most recent replies (status updates prefixed with @username posted by users who are friends with the user being replied to) to the authenticating user. 
        /// Defaults to http://twitter.com/statuses/user_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string RepliesTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(repliesTimelineUrl))
                    return TwitterServerUrl + "statuses/replies";
                else
                    return repliesTimelineUrl;
            }
            set { repliesTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the list of the 20 most recent direct messages sent to the authenticating user.  Defaults to http://twitter.com/direct_messages
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string DirectMessagesUrl
        {
            get
            {
                if (string.IsNullOrEmpty(directMessagesUrl))
                    return TwitterServerUrl + "direct_messages";
                else
                    return directMessagesUrl;
            }
            set { directMessagesUrl = value; }
        }

        /// <summary>
        /// Url to the Twitter HTTP Post. Defaults to http://twitter.com/statuses/update
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string UpdateUrl
        {
            get
            {
                if (string.IsNullOrEmpty(updateUrl))
                    return TwitterServerUrl + "statuses/update";
                else
                    return updateUrl;
            }
            set { updateUrl = value; }
        }

        /// <summary>
        /// Url to the user's friends. Defaults to http://twitter.com/statuses/friends
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string FriendsUrl
        {
            get
            {
                if (string.IsNullOrEmpty(friendsUrl))
                    return TwitterServerUrl + "statuses/friends";
                else
                    return friendsUrl;
            }
            set { friendsUrl = value; }
        }

        /// <summary>
        /// Url to the user's followers. Defaults to http://twitter.com/statuses/followers
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string FollowersUrl
        {
            get
            {
                if (string.IsNullOrEmpty(followersUrl))
                    return TwitterServerUrl + "statuses/followers";
                else
                    return followersUrl;
            }
            set { followersUrl = value; }
        }

        /// <summary>
        /// Returns extended information of a given user, specified by ID or screen name as per the required id parameter below.  
        /// This information includes design settings, so third party developers can theme their widgets according to a given user's preferences. 
        /// Defaults to http://twitter.com/users/show/
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string UserShowUrl
        {
            get
            {
                if (string.IsNullOrEmpty(userShowUrl))
                    return TwitterServerUrl + "users/show/";
                else
                    return userShowUrl;
            }
            set { userShowUrl = value; }
        }

        /// <summary>
        /// Url to sends a new direct message to the specified user from the authenticating user. Defaults to http://twitter.com/direct_messages/new
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string SendMessageUrl
        {
            get
            {
                if (string.IsNullOrEmpty(sendMessageUrl))
                    return TwitterServerUrl + "direct_messages/new";
                else
                    return sendMessageUrl;
            }
            set { sendMessageUrl = value; }
        }

        /// <summary>
        /// Url to destroy a status from the authenticating user. Defaults to http://twitter.com/statuses/destroy
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string DestroyUrl
        {
            get
            {
                if (string.IsNullOrEmpty(destroyUrl))
                    return TwitterServerUrl + "statuses/destroy/";
                else
                    return destroyUrl;
            }
            set { destroyUrl = value; }
        }

        /// <summary>
        /// Url to destroy a direct message from the authenticating user. Defaults to http://twitter.com/direct_messages/destroy/
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string DestroyDirectMessageUrl
        {
            get
            {
                if (string.IsNullOrEmpty(destroyDirectMessageUrl))
                    return TwitterServerUrl + "direct_messages/destroy/";
                else
                    return destroyDirectMessageUrl;
            }
            set { destroyDirectMessageUrl = value; }
        }



        public string CreateFriendshipUrl
        {
            get
            {
                if (string.IsNullOrEmpty(createFriendshipUrl))
                {
                    return TwitterServerUrl + "friendships/create/";
                }
                else
                {
                    return createFriendshipUrl;
                }
            }
            set { createFriendshipUrl = value; }
        }


        /// <summary>
        /// Returns the remaining number of API requests available to the requesting user before the API limit is reached for the current hour. 
        /// Calls to rate_limit_status do not count against the rate limit.  If authentication credentials are provided, the rate limit status 
        /// for the authenticating user is returned.  Otherwise, the rate limit status for the requester's IP address is returned. Defaults to 
        /// http://twitter.com/direct_messages/account/rate_limit_status
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string RateLimitStatusUrl
        {
            get
            {
                if (string.IsNullOrEmpty(rateLimitStatusUrl))
                {
                    return TwitterServerUrl + "account/rate_limit_status";
                }
                else
                {
                    return rateLimitStatusUrl;
                }
            }
            set { rateLimitStatusUrl = value; }
        }

        /// <summary>
        ///This is the url to return an individual tweet. 
        /// </summary>
        /// <remarks>
        /// Origionally done for "Conversations" but I'm sure it will be re-used.
        /// </remarks>
        public string TweetUrl
        {
            get
            {
                if (string.IsNullOrEmpty(tweetUrl))
                {
                    return TwitterServerUrl + "statuses/show/";
                }
                else
                {
                    return tweetUrl;
                }
            }
            set { tweetUrl = value; }
        }
        /// <summary>
        /// Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful; returns a 401 status
        /// code and an error message if not.  Use this method to test if supplied user credentials are valid. Defaults to
        /// http://twitter.com/account/verify_credentials
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string VerifyCredentialsUrl
        {
            get
            {
                if (string.IsNullOrEmpty(verifyCredentialsUrl))
                {
                    return TwitterServerUrl + "account/verify_credentials";
                }
                else
                {
                    return verifyCredentialsUrl;
                }
            }
            set { verifyCredentialsUrl = value; }
        }

        /// <summary>
        /// The format of the results from the twitter API. Ex: .xml, .json, .rss, .atom. Defaults to ".xml"
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://groups.google.com/group/twitter-development-talk/web/api-documentation
        /// </remarks>
        public string Format
        {
            get
            {
                if (string.IsNullOrEmpty(format))
                    return ".xml";
                else
                    return format;
            }
            set { format = value; }
        }




        /// <summary>
        /// The number of characters available for the Tweet text. Defaults to 140.
        /// </summary>
        /// <remarks>
        /// This value should only be changed if the character limit on Twitter.com has been changed.
        /// </remarks>
        public static int CharacterLimit
        {
            get
            {
                if (characterLimit == 0)
                    return 140;
                else
                    return characterLimit;
            }
            set { characterLimit = value; }
        }

        /// <summary>
        /// The name of the current client. Defaults to "Witty"
        /// </summary>
        /// <remarks>
        /// This value can be changed if you're using this Library for your own twitter client
        /// </remarks>
        public string ClientName
        {
            get
            {
                if (string.IsNullOrEmpty(clientName))
                    return "witty";
                else
                    return clientName;
            }
            set { clientName = value; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// </summary>
        public TwitterNet()
        {
        }

        /// <summary>
        /// Authenticated constructor with Proxy.
        /// </summary>
        /// <remarks>
        /// JMF: Note that this constructor should not be necessary.  The default proxy
        /// (HttpWebRequest.DefaultWebProxy) needs to be set to the user-configured
        /// proxy setting during app startup so that WPF controls that access the 
        /// internet directly (like Image controls) can do so.
        /// </remarks>
        public TwitterNet(IWebProxy webProxy)
        {
            this.webProxy = webProxy;
        }

        #endregion

        #region Public Methods

        #region Timeline Methods

        /// <summary>
        /// Retrieves the public timeline
        /// </summary>
        public TweetCollection GetPublicTimeline()
        {
            return RetrieveTimeline(Timeline.Public);
        }

        /// <summary>
        /// Retrieves the public timeline. Narrows the result to after the since date.
        /// </summary>
        public TweetCollection GetPublicTimeline(string since)
        {
            return RetrieveTimeline(Timeline.Public, since);
        }

        /// <summary>
        /// Retrieves the friends timeline
        /// </summary>
        public TweetCollection GetFriendsTimeline()
        {
            if (IsLoggedIn())
                return RetrieveTimeline(Timeline.Friends);
            else
                return RetrieveTimeline(Timeline.Public);
        }

        /// <summary>
        /// Retrieves the friends timeline. Narrows the result to after the since date.
        /// </summary>
        public TweetCollection GetFriendsTimeline(string since)
        {
            if (IsLoggedIn())
                return RetrieveTimeline(Timeline.Friends, since);
            else
                return RetrieveTimeline(Timeline.Public, since);
        }

        public bool IsLoggedIn()
        {
            return currentLoggedInUser != null;
        }

        /// <summary>
        /// Retrieves the friends timeline. Narrows the result to after the since date.
        /// </summary>
        public TweetCollection GetFriendsTimeline(string since, string userId)
        {
            return RetrieveTimeline(Timeline.Friends, since, userId);
        }

        public TweetCollection GetUserTimeline(string userId)
        {
            return RetrieveTimeline(Timeline.User, string.Empty, userId);
        }

        public TweetCollection GetReplies()
        {
            return RetrieveTimeline(Timeline.Replies);
        }

        public TweetCollection GetReplies(string since)
        {
            return RetrieveTimeline(Timeline.Replies, since);
        }

        public TweetCollection GetConversation(long id)
        {
            TweetCollection tweets = new TweetCollection();
            Tweet tweet = RetrieveTweet(id);
            while (tweet.InReplyTo != null)
            {
                tweets.Add(tweet);
                id = (long)tweet.InReplyTo;
                tweet = RetrieveTweet(id);
            }

            tweets.Add(tweet);
            return tweets;
        }

        #endregion

        /// <summary>
        /// Returns the authenticated user's friends who have most recently updated, each with current status inline.
        /// </summary>
        public UserCollection GetFriends()
        {
            return GetFriends(CurrentlyLoggedInUser.Id);
        }

        /// <summary>
        /// Returns the user's friends who have most recently updated, each with current status inline.
        /// </summary>
        public UserCollection GetFriends(int userId)
        {
            try
            {
                UserCollection users = new UserCollection();

                //This is ugly - we're mapping from TweetSharp users to our internal Twitter class
                //This will all go away when we move wholesale to TweetSharp
                foreach (TwitterUser user in GetFriendsInternal())
                    users.Add(CreateUser(user));

                return users;
            }
            catch
            {
                return null;
            }
        }

        public IEnumerable<TwitterUser> GetFriendsInternal()
        {
            IEnumerable<TwitterUser> _friends;

            var twitter = FluentTwitter.CreateRequest()
                        .Account().VerifyCredentials()
                        .Configuration.UseGzipCompression() //now using compression for performance
                        .Users().GetFriends().For(username)
                        .CreateCursor()
                        .AsJson();
            _friends = GetAllCursorValues(twitter, s => s.AsUsers());

            return _friends; //return either the newly fetched list, or the cached copy
        }

        private static IEnumerable<T> GetAllCursorValues<T>(ITwitterLeafNode twitter, Func<TwitterResult, IEnumerable<T>> conversionMethod)
        {
            long? nextCursor = -1;
            var ret = new List<T>();
            do
            {
                twitter.Root.Parameters.Cursor = nextCursor;
                var response = twitter.Request();
                IEnumerable<T> values = conversionMethod(response);
                if (values != null)
                {
                    ret.AddRange(values);
                }
                nextCursor = response.AsNextCursor();
            } while (nextCursor.HasValue && nextCursor.Value != 0);
            return ret;
        }

        public User GetUser(int userId)
        {
            var user = CreateRequest().Users().ShowProfileFor(userId).Request().AsUser();
            return CreateUser(user);
        }

        public void PostPhoto(System.IO.FileInfo file, string text)
        {
            //Needs to be re-implemented since TweetSharp removed support.
            //Hammock supports it pretty well.
        }

        /// <summary>
        /// Delete a tweet from a users timeline
        /// </summary>
        /// <param name="id">id of the Tweet to delete</param>
        public void DestroyTweet(long id)
        {
            CreateRequest().Statuses().Destroy(id);
        }

        /// <summary>
        /// Destroy a direct message sent by a user
        /// </summary>
        /// <param name="id">id of the direct message to delete</param>
        public void DestroyDirectMessage(long id)
        {
            CreateRequest().DirectMessages().Destroy(id);
        }

        /// <summary>
        /// Post new tweet to Twitter
        /// </summary>
        /// <returns>newly added tweet</returns>
        public Tweet AddTweet(string text)
        {
            return AddTweet(text, 0);
        }

        public Tweet AddTweet(string text, long replyid)
        {
            Tweet tweet = new Tweet();
            tweet.IsDirectMessage = (text.StartsWith("d ", StringComparison.CurrentCultureIgnoreCase));

            if (string.IsNullOrEmpty(text))
                return null;

            var result = CreateRequest().Statuses().Update(text).InReplyToStatus(replyid).AsJson().Request().AsStatus();

            tweet.Text = text;
            tweet.Source = result.Source;
            tweet.DateCreated = result.CreatedDate;
            tweet.InReplyTo = result.InReplyToStatusId;
            tweet.User = currentLoggedInUser;

            return tweet;
        }

        protected string GetPropertyFromXml(XmlNode twitterNode, string propertyName)
        {
            if (twitterNode != null)
            {
                XmlNode propertyNode = twitterNode.SelectSingleNode(propertyName);
                if (propertyNode != null)
                {
                    return propertyNode.InnerText;
                }
            }
            return String.Empty;
        }

        private User CreateUser(XmlNode userNode)
        {
            User user = new User();

            if (userNode != null)
            {
                int temp = -1;
                int.TryParse(GetPropertyFromXml(userNode, "id"), out temp);
                user.Id = temp;

                user.Name = GetPropertyFromXml(userNode, "name");
                user.ScreenName = GetPropertyFromXml(userNode, "screen_name");
                user.ImageUrl = GetPropertyFromXml(userNode, "profile_image_url");
                user.SiteUrl = GetPropertyFromXml(userNode, "url");
                user.Location = GetPropertyFromXml(userNode, "location");
                user.Description = GetPropertyFromXml(userNode, "description");
                user.BackgroundColor = GetPropertyFromXml(userNode, "profile_background_color");
                user.TextColor = GetPropertyFromXml(userNode, "profile_text_color");
                user.LinkColor = GetPropertyFromXml(userNode, "profile_link_color");
                user.SidebarBorderColor = GetPropertyFromXml(userNode, "profile_sidebar_border_color");
                user.SidebarFillColor = GetPropertyFromXml(userNode, "profile_sidebar_fill_color");

                temp = 0;
                int.TryParse(GetPropertyFromXml(userNode, "friends_count"), out temp);
                user.FollowingCount = temp;

                temp = 0;
                int.TryParse(GetPropertyFromXml(userNode, "favourites_count"), out temp);
                user.FavoritesCount = temp;

                temp = 0;
                int.TryParse(GetPropertyFromXml(userNode, "statuses_count"), out temp);
                user.StatusesCount = temp;

                temp = 0;
                int.TryParse(GetPropertyFromXml(userNode, "followers_count"), out temp);
                user.FollowersCount = temp;
            }

            return user;
        }

        private User CreateUser(TwitterUser twitterUser)
        {
            User user = new User();

            if (twitterUser != null)
            {
                user.Id = twitterUser.Id;

                user.Name = twitterUser.Name;
                user.ScreenName = twitterUser.ScreenName;
                user.ImageUrl = twitterUser.ProfileImageUrl;
                user.SiteUrl = twitterUser.Url;
                user.Location = twitterUser.Location;
                user.Description = twitterUser.Description;
                user.BackgroundColor = twitterUser.ProfileBackgroundColor;
                user.TextColor = twitterUser.ProfileTextColor;
                user.LinkColor = twitterUser.ProfileLinkColor;
                user.SidebarBorderColor = twitterUser.ProfileSidebarBorderColor;
                user.SidebarFillColor = twitterUser.ProfileSidebarFillColor;
                user.FollowingCount = twitterUser.FriendsCount;
                user.FavoritesCount = twitterUser.FavouritesCount;
                user.StatusesCount = twitterUser.StatusesCount;
                user.FollowersCount = twitterUser.FollowersCount;
            }

            return user;
        }

        public IFluentTwitter CreateRequest()
        {
            return FluentTwitter
                .CreateRequest()
                .AuthenticateWith(
                    Properties.Settings.Default.ConsumerKey,
                    Properties.Settings.Default.ConsumerSecret,
                    AccessToken,
                    AccessTokenSecret);
        }

        public void FetchPin(string consumerKey, string consumerSecret)
        {
            // get an authenticated request token from twitter
            _requestToken = GetRequestToken(consumerKey, consumerSecret);
            if (_requestToken == null) return;

            // automatically starts the default web browser, sending the 
            // user to the authorization URL.
            FluentTwitter.CreateRequest()
                .Authentication
                .AuthorizeDesktop(consumerKey, consumerSecret, _requestToken.Token);
        }

        private static TweetSharp.Model.OAuthToken GetRequestToken(string consumerKey, string consumerSecret)
        {
            var requestToken = FluentTwitter.CreateRequest()
                .Authentication
                .GetRequestToken(consumerKey, consumerSecret);

            var response = requestToken.Request();

            var result = response.AsToken();
            if (result != null) return result;

            var error = response.AsError();

            var mesg = error != null ? error.ErrorMessage
                : !String.IsNullOrEmpty(response.ResponseHttpStatusDescription) ? response.ResponseHttpStatusDescription
                : "Unknown error. Possible space-time continuum anomaly.";

            MessageBox.Show(mesg);
            return null;
        }

        public OAuthToken Authenticate(string pin)
        {
            if (_requestToken == null) return null;
        	_pin = pin;

            // exchange the unauthenticated request token with an authenticated access token,
            // and remember to persist this authentication pair for future use

            var accessToken = FluentTwitter.CreateRequest()
                .Authentication
                .GetAccessToken(
                    Properties.Settings.Default.ConsumerKey,
                    Properties.Settings.Default.ConsumerSecret,
                    _requestToken.Token, 
                    _pin
                );

            var response = accessToken.Request();
            var result = response.AsToken();
            if (result == null || String.IsNullOrEmpty(result.Token))
            {
                var error = response.AsError() ?? new TwitterError();
                MessageBox.Show(error.ErrorMessage ?? "Authentication Failed");
                return null;
            }

            return result;
        }

        /// <summary>
        /// Authenticating with the provided credentials and retrieve the user's settings
        /// </summary>
        /// <returns></returns>
        public User Login(OAuthToken token)
        {
            User user = new User();

            AccessToken = token.Token;
            AccessTokenSecret = token.TokenSecret;

            // check that the stored authentication is valid
            var twitter = CreateRequest().Account().VerifyCredentials();
            try
            {
                var response = twitter.Request();
                if (response.IsTwitterError || response.IsNetworkError || response.IsFailWhale)
                {
                    return user;
                }

                var bogus = new System.Text.RegularExpressions.Regex("\"user\"\\:\\{\"id\"\\:\\d+\\}\\,");
                response.Response = bogus.Replace(response.Response, string.Empty);

                var retval = response.AsUser();
                if (retval != null) user = CreateUser(retval);
            }
            finally
            {
                currentLoggedInUser = user;
                username = user.ScreenName;
            }
            return user;
        }

        #region Secure String Members
        static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("WittyPasswordSalt");

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string EncryptString(System.Security.SecureString input)
        {
            byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
                System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)),
                entropy,
                System.Security.Cryptography.DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }

        public static SecureString ToSecureString(string input)
        {
            SecureString secure = new SecureString();
            foreach (char c in input)
            {
                secure.AppendChar(c);
            }
            secure.MakeReadOnly();
            return secure;
        }

        public static string ToInsecureString(SecureString input)
        {
            string returnValue = string.Empty;
            IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
            try
            {
                returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
            }
            return returnValue;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="encryptedData"></param>
        /// <returns></returns>
        public static SecureString DecryptString(string encryptedData)
        {
            try
            {
                byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
                    Convert.FromBase64String(encryptedData),
                    entropy,
                    System.Security.Cryptography.DataProtectionScope.CurrentUser);
                return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData));
            }
            catch
            {
                return new SecureString();
            }
        }
        #endregion

        /// <summary>
        /// Get rate limit status for the user
        /// </summary>
        /// <returns>String with the number of usages left out over the total, e.g. "93/100"</returns>
        public string RetrieveRateLimitStatus()
        {
            var rateLimitQuery = CreateRequest().Account().GetRateLimitStatus();
	        var response = rateLimitQuery.Request();
	        var rateLimitStatus = response.AsRateLimitStatus();

            if (rateLimitStatus != null)
                return string.Format("{0}/{1}", rateLimitStatus.RemainingHits, rateLimitStatus.HourlyLimit);
            else
                return string.Empty;
        }

        public DirectMessageCollection RetrieveMessages()
        {
            return RetrieveMessages(null);
        }

        /// <summary>
        /// Gets direct messages for the user
        /// </summary>
        /// <returns>Collection of direct messages</returns>
        public DirectMessageCollection RetrieveMessages(long? since)
        {
            DirectMessageCollection messages = new DirectMessageCollection();

            var request = CreateRequest().DirectMessages().Received();
            if (since.HasValue)
                request = request.Since(since.Value);
            else
                request = request.Take(50);

                foreach (var msg in request.Request().AsDirectMessages())
                {
                    DirectMessage message = new DirectMessage();
                    message.Id = msg.Id;
                    message.Text = msg.Text;
                    message.DateCreated = msg.CreatedDate;
                    User sender = CreateUser(msg.Sender);
                    message.Sender = sender;
                    User recipient = currentLoggedInUser;
                    message.Recipient = recipient;

                    messages.Add(message);
                };

            return messages;
        }

        public Tweet RetrieveTweet(long id)
        {
            Tweet tweet = new Tweet();
            var result = CreateRequest().Statuses().Show(id).AsJson().Request().AsStatus();

            return GetTweetFromTwitterStatus(result);
        }

        private Tweet GetTweetFromTwitterStatus(TwitterStatus result)
        {
            Tweet tweet = new Tweet();

            if (result != null)
            {
                tweet.Id = result.Id;
                tweet.Text = result.Text;
                string source = result.Source;
                tweet.DateCreated = result.CreatedDate;
                tweet.InReplyTo = result.InReplyToStatusId;
                tweet.User = CreateUser(result.User);
                tweet.IsReply = result.InReplyToScreenName != null 
                    && result.InReplyToScreenName == CurrentlyLoggedInUser.ScreenName;
            }
            return tweet;
        }

        public void SendMessage(string user, string text)
        {
            CreateRequest().DirectMessages().Send(user, text);
        }

        /// <summary>
        /// Follow the user specified by userId
        /// </summary>
        /// <param name="userId"></param>
        public void FollowUser(string userName)
        {
            if (String.Compare(userName, CurrentlyLoggedInUser.ScreenName, true) == 0)
                return;

            CreateRequest().Friendships().Befriend(username);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Retrieves the specified timeline from Twitter
        /// </summary>
        /// <returns>Collection of Tweets</returns>
        private TweetCollection RetrieveTimeline(Timeline timeline)
        {
            return RetrieveTimeline(timeline, string.Empty);
        }

        /// <summary>
        /// Retrieves the specified timeline from Twitter
        /// </summary>
        /// <returns>Collection of Tweets</returns>
        private TweetCollection RetrieveTimeline(Timeline timeline, string since)
        {
            return RetrieveTimeline(timeline, since, string.Empty);
        }

        /// <summary>
        /// The Main function for interfacing with the Twitter API
        /// </summary>
        /// <returns>Collection of Tweets. Twitter limits the max to 20.</returns>
        private TweetCollection RetrieveTimeline(Timeline timeline, string since, string userId)
        {
            TweetCollection tweets = new TweetCollection();

            ITwitterLeafNode request;

            switch (timeline)
            {
                case Timeline.Public:
                    request = CreateRequest().Statuses().OnPublicTimeline();
                    break;
                case Timeline.Friends:
                    request = CreateRequest().Statuses().OnFriendsTimeline();
                    break;
                case Timeline.User:
                    request = CreateRequest().Statuses().OnUserTimeline().For(userId);
                    break;
                case Timeline.Replies:
                    request = CreateRequest().Statuses().Mentions();
                    break;
                default:
                    request = CreateRequest().Statuses().OnPublicTimeline();
                    break;
            }

            if (!string.IsNullOrEmpty(since))
            {
                DateTime sinceDate;
                DateTime.TryParse(since, out sinceDate);

                // Go back a minute to compensate for latency.
                sinceDate = sinceDate.AddMinutes(-1);

                //TODO: Need to make use of since information
            }

            foreach (var result in request.Request().AsStatuses())
            {
                tweets.Add(GetTweetFromTwitterStatus(result));
            }

            tweets.SaveToDisk();
            return tweets;
        }

        private bool IsReplyTweet(string userId, Tweet tweet)
        {
            if (tweet.Timeline == Timeline.Replies)
                return true;

            if (tweet.Timeline == Timeline.Friends)
            {
                if (tweet.Text.Contains("@" + userId))
                    return true;
            }

            return false;
        }

        private void ParseWebException(WebException webExcp)
        {
            ParseWebException(webExcp, null, null);
        }

        private void ParseWebException(WebException webExcp, Timeline? timeline, string userId)
        {
            // Get the WebException status code.
            WebExceptionStatus status = webExcp.Status;
            // If status is WebExceptionStatus.ProtocolError, 
            //   there has been a protocol error and a WebResponse 
            //   should exist. Display the protocol error.
            if (status == WebExceptionStatus.ProtocolError)
            {
                // Get HttpWebResponse so that you can check the HTTP status code.
                HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;

                switch (httpResponse.StatusCode)
                {
                    case HttpStatusCode.NotModified: // no new tweets so ignore error.
                        break;

                    case HttpStatusCode.BadRequest: // rate limit exceeded
                        string message = String.Format("Rate limit exceeded. You have {0} of your requests left. Please try again in a few minutes", RetrieveRateLimitStatus());
                        throw new RateLimitException(message);

                    case HttpStatusCode.Unauthorized:
                        throw new SecurityException("Not Authorized.");

                    case HttpStatusCode.NotFound: // specified user does not exist
                        if (timeline == Timeline.User)
                            throw new UserNotFoundException(userId, "@" + userId + " does not exist (probably mispelled)");
                        else // what if a 404 happens to occur in another scenario?
                            throw webExcp;

                    case HttpStatusCode.ProxyAuthenticationRequired:
                        throw new ProxyAuthenticationRequiredException("Proxy authentication required.");

                    case HttpStatusCode.RequestTimeout:
                        throw new RequestTimeoutException("Request timed out. We'll try again in a few minutes");

                    case HttpStatusCode.BadGateway: //Twitter is freaking out.
                        throw new BadGatewayException("Fail Whale!  There was a problem calling the Twitter API.  Please try again in a few minutes.");

                    default:
                        throw webExcp;
                }
            }
            else if (status == WebExceptionStatus.ProxyNameResolutionFailure)
            {
                throw new ProxyNotFoundException("The proxy server could not be found.  Check that it was entered correctly in the Options dialog.  You may need to disable your web proxy in the Options, if for instance you use a proxy server at work and are now at home.");
            }
            else throw webExcp;
        }

        #endregion
    }

    /// <summary>
    /// Enumeration of available Twitter timelines
    /// </summary>
    public enum Timeline
    {
        Public,
        Friends,
        User,
        Replies,
        DirectMessages
    }
}
